import { JKManager } from "../JKManager";

const { ccclass, property } = cc._decorator;

let BufferAreaCoefficient = 3, // 屏幕外缓冲区大小系数
    StartFillItemActTime = 1, // 初始反向填充显示区域时，添加一项的插入动画时间
    DeltaPositionLimit = 5, // 滑动过程中，触发逻辑处理的最小移动量
    MaxInertiaScrollVelocity = 5000, // 最大惯性速度
    SelectDeltaPositionLimit = 8, // 选择模式最小自动移动量
    SelcetActionTime = 0.8, // 选择模式锁定动画时间，秒
    PageDeltaPositionLimit = 9999, // 轮播模式最小自动移动量
    PageSpeedLimit = 25, // 轮播模式最小移动速度
    PageActionTime = 0.3, // 轮播模式翻页时间，秒
    MinTouchMovedDis = 30, // 触发界面滑动的最小触摸滑动距离
    MouseWheelRatio = 10, // 鼠标滚轮滑动速度加速倍数
    InsertOrDeleteItemActTime = 0.2 // 插入或删除列表项的动画时间

type IsvItem = cc.Node & {
    poolName?: string;
    isvIndex: number;
    deleteActCall?: () => void;
    insertActCall?: () => void;
}

type IsvNodePools = {
    [key: string]: cc.NodePool,
}

type IsvStatics = {
    [key: string]: IsvItem,
}

export type IsvItemObj = {
    poolName: string;
    item: cc.Node;
    static?: boolean;
}

//#region 处理嵌套问题
(cc.ScrollView.prototype as any).start = function () {
    this._calculateBoundary();
    //Because widget component will adjust content position and scrollview position is correct after visit
    //So this event could make sure the content is on the correct position after loading.
    if (this.content) {
        cc.director.once(cc.Director.EVENT_BEFORE_DRAW, this._adjustContentOutOfBoundary, this);
    }

    this._childSvs = this._childSvs || []
    let checkParent = (child: cc.Node) => {
        if (child.parent && !(child.parent instanceof cc.Scene)) {
            this._parentSv = child.parent.getComponent(cc.ScrollView)
            if (!this._parentSv) {
                checkParent(child.parent)
            } else {
                this._parentSv._childSvs = this._parentSv._childSvs || []
                this._parentSv._childSvs.enabledInHierarchy && this._parentSv._childSvs.push(this)
            }
        }
    }
    checkParent(this.node)
};
(cc.ScrollView.prototype as any).onEnable = function () {
    if (!CC_EDITOR) {
        this._registerEvent();
        if (this.content) {
            this.content.on(cc.Node.EventType.SIZE_CHANGED, this._calculateBoundary, this);
            this.content.on(cc.Node.EventType.SCALE_CHANGED, this._calculateBoundary, this);
            if (this._view) {
                this._view.on(cc.Node.EventType.POSITION_CHANGED, this._calculateBoundary, this);
                this._view.on(cc.Node.EventType.SCALE_CHANGED, this._calculateBoundary, this);
                this._view.on(cc.Node.EventType.SIZE_CHANGED, this._calculateBoundary, this);
            }
        }
    }
    this._updateScrollBarState();

    this._childSvs = this._childSvs || []
    let checkParent = (child) => {
        if (child.parent && !(child.parent instanceof cc.Scene)) {
            this._parentSv = child.parent.getComponent(cc.ScrollView)
            if (!this._parentSv) {
                checkParent(child.parent)
            } else {
                this._parentSv._childSvs = this._parentSv._childSvs || []
                this._parentSv.enabledInHierarchy && this._parentSv._childSvs.push(this)
            }
        }
    }
    checkParent(this.node)
};
(cc.ScrollView.prototype as any)._onTouchBegan = function (event: cc.Event.EventTouch, captureListeners) {
    this.moving = null
    if (!this.enabledInHierarchy) return

    let touch = event.touch
    if (this.content) {
        this._handlePressLogic(touch)
    }
    this._touchMoved = false
    this._stopPropagationIfTargetIsMe(event)
};
(cc.ScrollView.prototype as any)._onTouchMoved = function (event, captureListeners) {
    let childMoving = false

    for (let index = 0; index < this._childSvs.length; index++) {
        let childSv = this._childSvs[index]
        childMoving = childSv.moving
        if (childSv.horizontal === this.horizontal) {
            let touch = event.touch,
                start = touch.getStartLocation()
            if (childSv.content.parent.getBoundingBoxToWorld().contains(start)) {
                childMoving = true
            }
        }
        if (childMoving) {
            break
        }
    }
    if (!this.moveLocked && !childMoving) {
        if (!this._parentSv || !this._parentSv.moving) {
            if (!this.enabledInHierarchy) return

            let touch = event.touch,
                deltaMove = touch.getLocation().sub(touch.getStartLocation())
            if (this.content) {
                if (this.moving || (this.moving === null && ((Math.abs(deltaMove.x) >= MinTouchMovedDis && this.horizontal) || (Math.abs(deltaMove.y) >= MinTouchMovedDis && !this.horizontal)))) {
                    if (this.moving === null) {
                        this._childSvs.forEach((childSv: cc.ScrollView & { moving: boolean }) => {
                            childSv.moving = false
                        })
                        if (this._parentSv) {
                            this._parentSv.moving = false
                            if (this._parentSv.horizontal === this.horizontal) {
                                // this._parentSv.enabled = false
                            }
                        }
                    }
                    this.moving = true
                    this._handleMoveLogic(touch)
                } else if (this.moving === null && ((Math.abs(deltaMove.x) >= MinTouchMovedDis && this.horizontal) || (Math.abs(deltaMove.y) >= MinTouchMovedDis && !this.horizontal))) {
                    this.moving = false
                } else {
                    return
                }
            }
            // Do not prevent touch events in inner nodes
            if (!this.cancelInnerEvents) {
                return
            }

            //FIXME: touch move delta should be calculated by DPI.
            if (this.moving) {
                if (!this._touchMoved && event.target !== this.node) {
                    // Simulate touch cancel for target node
                    let cancelEvent = new cc.Event.EventTouch(event.getTouches(), event.bubbles)
                    cancelEvent.type = cc.Node.EventType.TOUCH_CANCEL
                    cancelEvent.touch = event.touch
                    cancelEvent[`simulate`] = true
                    event.target.dispatchEvent(cancelEvent)
                    this._touchMoved = true
                }
            }
            this._stopPropagationIfTargetIsMe(event)
        }
    }
};
(cc.ScrollView.prototype as any)._onTouchEnded = function (event: cc.Event.EventTouch, captureListeners) {
    if (this.moving) {
        if (!this.enabledInHierarchy) return

        this._dispatchEvent('touch-up')

        let touch = event.touch
        if (this.content) {
            this._handleReleaseLogic(touch)
        }
        if (this._touchMoved) {
            event.stopPropagation()
        } else {
            this._stopPropagationIfTargetIsMe(event)
        }

        if (this._parentSv) {
            this._parentSv.enabled = true
        }
    }
    this.moving = false
};
(cc.ScrollView.prototype as any)._onTouchCancelled = function (event: cc.Event.EventTouch, captureListeners: unknown[]) {
    if (!this.enabledInHierarchy) return;
    // if (this.hasNestedViewGroup(event, captureListeners)) return;

    // Filte touch cancel event send from self
    if (!(event as any).simulate) {
        let touch = event.touch;
        if (this.content) {
            this._handleReleaseLogic(touch);

            if (this._parentSv) {
                this._parentSv.enabled = true
            }
            this.moving = false
        }
    }
    this._stopPropagationIfTargetIsMe(event);
};
(cc.ScrollView.prototype as any)._onMouseWheel = function (event, captureListeners) {
    if (!this.enabledInHierarchy) return;
    if (this.hasNestedViewGroup(event, captureListeners)) return;

    let deltaMove = cc.v2(0, 0);
    let wheelPrecision = -0.1;
    //On the windows platform, the scrolling speed of the mouse wheel of ScrollView on chrome and firebox is different
    if (cc.sys.os === cc.sys.OS_WINDOWS && cc.sys.browserType === cc.sys.BROWSER_TYPE_FIREFOX) {
        wheelPrecision = -0.1 / 3;
    }
    if (CC_JSB || CC_RUNTIME) {
        wheelPrecision = -7;
    }
    if (this.vertical) {
        deltaMove = cc.v2(0, event.getScrollY() * wheelPrecision * MouseWheelRatio);
    }
    else if (this.horizontal) {
        deltaMove = cc.v2(event.getScrollY() * wheelPrecision * MouseWheelRatio, 0);
    }

    this._mouseWheelEventElapsedTime = 0;
    this._processDeltaMove(deltaMove);

    if (!this._stopMouseWheel) {
        this._handlePressLogic();
        this.schedule(this._checkMouseWheel, 1.0 / 60);
        this._stopMouseWheel = true;
    }

    this._stopPropagationIfTargetIsMe(event);
};
(cc.ScrollView.prototype as any)._calculateTouchMoveVelocity = function () {
    let totalTime = 0;
    totalTime = this._touchMoveTimeDeltas.reduce(function (a, b) {
        return a + b;
    }, totalTime);

    if (totalTime <= 0 || totalTime >= 0.5) {
        return cc.v2(0, 0);
    }

    let totalMovement = cc.v2(0, 0);
    totalMovement = this._touchMoveDisplacements.reduce(function (a, b) {
        return a.add(b) > MaxInertiaScrollVelocity ? MaxInertiaScrollVelocity : a.add(b)
    }, totalMovement);

    let result = cc.v2(totalMovement.x * (1 - this.brake) / totalTime,
        totalMovement.y * (1 - this.brake) / totalTime)

    if (result.mag() > MaxInertiaScrollVelocity) {
        result.scaleSelf(cc.v2(MaxInertiaScrollVelocity / result.mag(), MaxInertiaScrollVelocity / result.mag()))
    }

    return result
};
(cc.PageView.prototype as any).start = function () {
    (cc.ScrollView.prototype as any).start.call(this)
};
(cc.PageView.prototype as any).hasNestedViewGroup = function (event: cc.Event.EventTouch, captureListeners: unknown[]) {
    (cc.ScrollView.prototype as any).hasNestedViewGroup.call(this, event, captureListeners)
};
(cc.PageView.prototype as any)._onTouchBegan = function (event: cc.Event.EventTouch, captureListeners: unknown[]) {
    this._touchBeganPosition = event.touch.getLocation();
    (cc.ScrollView.prototype as any)._onTouchBegan.call(this, event, captureListeners)
};
(cc.PageView.prototype as any)._onTouchMoved = function (event: cc.Event.EventTouch, captureListeners: unknown[]) {
    (cc.ScrollView.prototype as any)._onTouchMoved.call(this, event, captureListeners)
};
(cc.PageView.prototype as any)._autoScrollToPage = function () {
    var bounceBackStarted = this._startBounceBackIfNeeded();
    if (bounceBackStarted) {
        let bounceBackAmount = this._getHowMuchOutOfBoundary();
        bounceBackAmount = this._clampDelta(bounceBackAmount);
        if (bounceBackAmount.x > 0 || bounceBackAmount.y < 0) {
            this._curPageIdx = this._pages.length === 0 ? 0 : this._pages.length - 1;
        }
        if (bounceBackAmount.x < 0 || bounceBackAmount.y > 0) {
            this._curPageIdx = 0;
        }

        if (this.indicator) {
            this.indicator._changedState();
        }
        this._lastPageIdx !== this._curPageIdx && this.node.emit(`start-turning`, this)
    }
    else {
        var moveOffset = this._touchBeganPosition.sub(this._touchEndPosition);
        var index = this._curPageIdx, nextIndex = index + this._getDragDirection(moveOffset);
        var timeInSecond = this.pageTurningSpeed * Math.abs(index - nextIndex);
        if (nextIndex < this._pages.length) {
            if (this._isScrollable(moveOffset, index, nextIndex)) {
                this.scrollToPage(nextIndex, timeInSecond);
                return;
            }
            else {
                var touchMoveVelocity = this._calculateTouchMoveVelocity();
                if (this._isQuicklyScrollable(touchMoveVelocity)) {
                    this.scrollToPage(nextIndex, timeInSecond);
                    return;
                }
            }
        }
        this.scrollToPage(index, timeInSecond);
    }
}
cc.PageView.prototype.scrollToPage = function (idx, timeInSecond) {
    if (idx < 0 || idx >= this._pages.length)
        return;
    timeInSecond = timeInSecond !== undefined ? timeInSecond : 0.3;
    this._curPageIdx = idx;
    this.scrollToOffset(this._moveOffsetValue(idx), timeInSecond, true);
    if (this.indicator) {
        this.indicator._changedState();
    }
    this._lastPageIdx !== this._curPageIdx && this.node.emit(`start-turning`, this)
}
cc.PageView.prototype.setCurrentPageIndex = function (index, time = this.pageTurningSpeed) {
    this.scrollToPage(index, time === this.pageTurningSpeed ? this.pageTurningSpeed : time)
}
//#endregion

export type IsvMode =
    `Nomal` |
    `Select` | // 停止手动滑动时，自动滑动到最近的项上
    `Page` // 轮播

export type IsvPageExtraMode =
    `Nomal` |
    `Scale` // 缩放

// 无限滑动列表组件
@ccclass
export class InfiniteScrollViewCpnt extends cc.Component {
    @property(cc.ScrollView)
    scrollView: cc.ScrollView = null
    @property(cc.Integer)
    totalCount: number = 0
    @property(cc.Integer)
    itemTopOrLeftSpace: number = 0
    @property(cc.Integer)
    itemSpace: number = 0
    @property(cc.Integer)
    itemBottomOrRightSpace: number = 0
    @property(cc.Boolean)
    isHorizontal: boolean = false
    @property(cc.Node)
    ndCenterPos: cc.Node = null
    @property({
        tooltip: CC_DEV && `i18n:COMPONENT.layout.affected_by_scale`
    })
    affectedByScale: boolean = true
    @property(cc.Boolean)
    _isScrollBar = false
    @property(cc.Boolean)
    get isScrollBar() {
        return this._isScrollBar
    }
    set isScrollBar(isScrollBar) {
        this._isScrollBar = isScrollBar
        this.ndScrollBarCtn.active = this._isScrollBar
    }
    @property(cc.Boolean)
    _scrollBarEnableAutoHide = false
    @property(cc.Boolean)
    get scrollBarEnableAutoHide() {
        return this._scrollBarEnableAutoHide
    }
    set scrollBarEnableAutoHide(scrollBarEnableAutoHide) {
        this._scrollBarEnableAutoHide = scrollBarEnableAutoHide
        this.scrollBarH.enableAutoHide = this._scrollBarEnableAutoHide
        this.scrollBarV.enableAutoHide = this._scrollBarEnableAutoHide
    }
    @property(cc.Float)
    _scrollBarAutoHideTime = 1
    @property(cc.Float)
    get scrollBarAutoHideTime() {
        return this._scrollBarAutoHideTime
    }
    set scrollBarAutoHideTime(scrollBarAutoHideTime) {
        this._scrollBarAutoHideTime = scrollBarAutoHideTime
        this.scrollBarH.autoHideTime = this._scrollBarAutoHideTime
        this.scrollBarV.autoHideTime = this._scrollBarAutoHideTime
    }

    ndItem: cc.Node
    ndSvCtn: cc.Node;
    wdgtSvCtn: cc.Widget;
    ndSvView: cc.Node;
    isVertical: boolean;
    wdgtSvView: cc.Widget;
    scrollBarV: cc.Scrollbar;
    scrollBarH: cc.Scrollbar;
    ndScrollBarCtn: cc.Node;
    private _theoreticalMaxItemSize: number;
    private _buffer: number;
    private _startIndex: number;
    private _endIndex: number;
    private _updateTimer: number;
    private _lastContentPosX: number;
    private _lastContentPosY: number;
    private _curItemsSize: number;
    private _lastSelectedItem: IsvItem;
    private _totalCount: number;
    private _lastPagedItem: IsvItem;
    private _scrollBegan: boolean;
    private _isShow: boolean;
    private _items: (IsvItem)[];
    private _mode: IsvMode;
    private _selectCall: (item: cc.Node, index: number, isCurItem: boolean, isFinalItem: boolean) => void;
    private _pageExtraMode: IsvPageExtraMode;
    private _maxPageScale: number;
    private _minPageScale: number;
    private _pageCall: (item: cc.Node, index: number, isCurItem: boolean, isFinalItem: boolean) => void;
    private _lastFinalPagedIndex: number;
    private _selecting: boolean;
    private _lastFinalSelectedIndex: number;
    private _paging: boolean;
    private _onBounceTop: () => void;
    private _onBounceBottom: () => void;
    private _onScrollBegan: () => void;
    private _startFillPosReverse: boolean;
    private _ndSvViewActing: boolean;
    private _tempFinalPageItem: cc.Node & { poolName?: string; isvIndex: number; };
    private _nodePools: IsvNodePools = cc.js.createMap();
    private _isIniting: boolean;
    private _statics: IsvStatics = cc.js.createMap();

    onLoad() {
        cc.log(`${cc.js.getClassName(this)} onLoad`)

        this.ndItem = cc.find(`scrollView/view/content/itemForTest`, this.node)
        this.ndItem.parent = null
        this.ndSvCtn = this.scrollView.content
        this.ndSvCtn.height = 0
        this.wdgtSvCtn = this.ndSvCtn.getComponent(cc.Widget)
        this.ndSvView = this.ndSvCtn.parent
        this.wdgtSvView = this.ndSvView.getComponent(cc.Widget)
        this.scrollBarV = this.scrollView.horizontalScrollBar
        this.scrollBarH = this.scrollView.verticalScrollBar
        this.ndScrollBarCtn = this.scrollBarV.node.parent
        this.isScrollBar = this.isScrollBar
        this._theoreticalMaxItemSize = 0
        this._buffer = 0
        this._items = []
        this._startIndex = 0
        this._endIndex = -1
        this._updateTimer = 0
        this._lastContentPosX = 0
        this._lastContentPosY = 0
        this._curItemsSize = this.itemTopOrLeftSpace + this.itemBottomOrRightSpace - this.itemSpace * 2

        this.setMode(`Nomal`)
        // this.init(this.totalCount, (index) => {
        //     // cc.log(`${cc.js.getClassName(this)} _createCall index = ${index}`)

        //     let item = this.getItemFromPoolWithName(this.ndItem.name) || cc.instantiate(this.ndItem)
        //     let lab = item.getChildByName(`lab`).getComponent(cc.Label)
        //     lab.string = index.toString()

        //     return {
        //         item: item,
        //         poolName: item.name,
        //     }
        // }, (item, index, isActive) => {
        //     // cc.log(`${cc.js.getClassName(this)} _activeCall index = ${index} isActive = ${isActive}`)

        // }, !this.isHorizontal)
    }

    update(dt: number) {
        let needToStop = true,
            dDis = 0,
            isRight: boolean,
            isDown: boolean
        if (this.isHorizontal) {
            dDis = Math.abs(this.ndSvCtn.x - this._lastContentPosX)
            isRight = this.ndSvCtn.x > this._lastContentPosX
            this._lastContentPosX = this.ndSvCtn.x
            if (dDis > DeltaPositionLimit) {
                needToStop = false
                if (this.scrollView.isAutoScrolling() || dt <= 0) {
                    let putItems = []
                    let needAddLeft = false
                    let needAddRight = false
                    for (let index = 0; index < this._items.length; index++) {
                        let item = this._items[index]
                        let viewPos = this._getPositionInView(item)
                        let leftOffset = -viewPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this._buffer
                        let rightOffset = viewPos.x + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) - this._buffer
                        if (isRight) {
                            // 如果往右滚动时item已经超出右缓冲矩形，就回收节点
                            if (rightOffset > item.width * (this.affectedByScale ? item.scaleX : 1)) {
                                putItems.push(item)
                                this._endIndex--
                            }
                        } else {
                            // 如果往左滚动时item已经超出左缓冲矩形，就回收节点
                            if (leftOffset > item.width * (this.affectedByScale ? item.scaleX : 1)) {
                                putItems.push(item)
                                this._startIndex++
                            }
                        }
                        // 如果左缓冲矩形内节点不足，就添加节点
                        if (index === 0 && leftOffset < 0) {
                            needAddLeft = true
                        }
                        // 如果右缓冲矩形内节点不足，就添加节点
                        if (index === this._items.length - 1 && rightOffset < 0) {
                            needAddRight = true
                        }
                    }
                    this._putItems(putItems)
                    needAddRight && this._addRightItems()
                    needAddLeft && this._addLeftItems()
                }
            }
        } else {
            dDis = Math.abs(this.ndSvCtn.y - this._lastContentPosY)
            isDown = this.ndSvCtn.y < this._lastContentPosY
            this._lastContentPosY = this.ndSvCtn.y
            if (dDis > DeltaPositionLimit) {
                needToStop = false
                if (this.scrollView.isAutoScrolling() || dt <= 0) {
                    let putItems = []
                    let needAddTop = false
                    let needAddBottom = false
                    for (let index = 0; index < this._items.length; index++) {
                        let item = this._items[index]
                        let viewPos = this._getPositionInView(item)
                        let topOffset = viewPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) - this._buffer
                        let bottomOffset = - viewPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this._buffer
                        if (isDown) {
                            // 如果往下滚动时item已经超出下缓冲矩形，就回收节点
                            if (bottomOffset > item.height * (this.affectedByScale ? item.scaleY : 1)) {
                                putItems.push(item)
                                this._endIndex--
                            }
                        } else {
                            // 如果往上滚动时item已经超出上缓冲矩形，就回收节点
                            if (topOffset > item.height * (this.affectedByScale ? item.scaleY : 1)) {
                                putItems.push(item)
                                this._startIndex++
                            }
                        }
                        // 如果上缓冲矩形内节点不足，就添加节点
                        if (index === 0 && topOffset < 0) {
                            needAddTop = true
                        }
                        // 如果下缓冲矩形内节点不足，就添加节点
                        if (index === this._items.length - 1 && bottomOffset < 0) {
                            needAddBottom = true
                        }
                    }
                    this._putItems(putItems)
                    needAddBottom && this._addBottomItems()
                    needAddTop && this._addTopItems()
                }
            }
        }
        this.ndSvCtn.x = this._lastContentPosX
        this.ndSvCtn.y = this._lastContentPosY

        switch (this._mode) {
            case `Select`:
                if (this._selectCall) {
                    let noContains = true,
                        targetPos = this.node.convertToWorldSpaceAR(this.ndCenterPos.position),
                        doSelect = (item: IsvItem) => {
                            this._lastSelectedItem && this._lastSelectedItem !== item && this._selectCall(this._lastSelectedItem, this._lastSelectedItem.isvIndex, false, false)
                            if (!this._selecting && ((!this.scrollView.isScrolling() && !this.scrollView.isAutoScrolling()) || (this.scrollView.isAutoScrolling() && (needToStop || dDis <= SelectDeltaPositionLimit)))) {
                                this.scrollView.stopAutoScroll()
                                this._selecting = true
                                this.scrollToItem(item, SelcetActionTime)
                                this._lastFinalSelectedIndex !== item.isvIndex && this._selectCall(item, item.isvIndex, true, true)
                                this._lastFinalSelectedIndex = item.isvIndex
                            } else {
                                this._lastSelectedItem !== item && this._selectCall(item, item.isvIndex, true, false)
                            }
                            this._lastSelectedItem = item
                        }
                    for (let index = 0; index < this._items.length; index++) {
                        let item = this._items[index],
                            worldPos = item.parent.convertToWorldSpaceAR(item.position),
                            startPos: cc.Vec2,
                            endPos: cc.Vec2,
                            rect: cc.Rect
                        if (item.isvIndex === 0) {
                            if (this.isHorizontal) {
                                startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemTopOrLeftSpace - this.itemSpace, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace)
                                endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemSpace * 0.5, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace * 0.5)
                                rect = cc.Rect.fromMinMax(startPos, endPos)
                            } else {
                                startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemSpace * 0.5, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace * 0.5)
                                endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemTopOrLeftSpace - this.itemSpace, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace)
                                rect = cc.Rect.fromMinMax(startPos, endPos)
                            }
                        } else if (item.isvIndex === this._totalCount - 1) {
                            if (this.isHorizontal) {
                                startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemSpace * 0.5, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace * 0.5)
                                endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemBottomOrRightSpace - this.itemSpace, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace)
                                rect = cc.Rect.fromMinMax(startPos, endPos)
                            } else {
                                startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemBottomOrRightSpace + this.itemSpace, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace)
                                endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemSpace * 0.5, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace * 0.5)
                                rect = cc.Rect.fromMinMax(startPos, endPos)
                            }
                        } else {
                            startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemSpace * 0.5, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace * 0.5)
                            endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemSpace * 0.5, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace * 0.5)
                            rect = cc.Rect.fromMinMax(startPos, endPos)
                        }
                        if (rect.contains(cc.v2(targetPos.x, targetPos.y))) {
                            noContains = false
                            doSelect(item)

                            break
                        }
                    }
                    if (noContains) {
                        let item = this._lastSelectedItem
                        item && doSelect(item)
                    }
                }

                break
            case `Page`:
                if (this._pageCall) {
                    let noContains = true,
                        targetPos = this.node.convertToWorldSpaceAR(this.ndCenterPos.position),
                        doPage = (item: IsvItem) => {
                            this._lastPagedItem && this._lastPagedItem !== item && this._pageCall(this._lastPagedItem, this._lastPagedItem.isvIndex, false, false)
                            let isScrolling = this.scrollView.isScrolling(),
                                isAutoScrolling = this.scrollView.isAutoScrolling()
                            if (!this._paging &&
                                ((!isScrolling && !isAutoScrolling) || (isAutoScrolling && (needToStop || dDis <= PageDeltaPositionLimit)))
                            ) {
                                let firstRestItem: IsvItem
                                if (isRight !== undefined || isDown !== undefined) {
                                    if (dt > 0) {
                                        let speed = dDis / dt
                                        if (this._lastFinalPagedIndex === item.isvIndex && speed >= PageSpeedLimit) {
                                            let lastFinalPagedIndex = this._lastFinalPagedIndex
                                            // cc.log(`isRight = ${isRight}`)
                                            // cc.log(`isDown = ${isDown}`)
                                            if (isRight || isDown) {
                                                lastFinalPagedIndex--
                                            } else {
                                                lastFinalPagedIndex++
                                            }
                                            firstRestItem = this._getItemByIndex(lastFinalPagedIndex)
                                        }
                                    }
                                }
                                this.scrollView.stopAutoScroll()
                                this._paging = true
                                if (firstRestItem) {
                                    this._lastFinalPagedIndex = firstRestItem.isvIndex
                                    this.scrollToItem(firstRestItem, dt > 0 ? PageActionTime : 0)
                                } else {
                                    this.scrollToItem(item, dt > 0 ? PageActionTime : 0)
                                    if (this._lastFinalPagedIndex !== item.isvIndex) {
                                        this._lastFinalPagedIndex = item.isvIndex
                                    }
                                }
                            } else {
                                this._lastPagedItem !== item && this._pageCall(item, item.isvIndex, true, false)
                            }
                            this._lastPagedItem = item
                        }
                    for (let index = 0; index < this._items.length; index++) {
                        let item = this._items[index],
                            worldPos = item.parent.convertToWorldSpaceAR(item.position),
                            startPos: cc.Vec2,
                            endPos: cc.Vec2,
                            rect: cc.Rect
                        if (item.isvIndex === 0) {
                            if (this.isHorizontal) {
                                startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemTopOrLeftSpace + this.itemSpace, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace)
                                endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemSpace * 0.5, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace * 0.5)
                                rect = cc.Rect.fromMinMax(startPos, endPos)
                            } else {
                                startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemSpace * 0.5, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace * 0.5)
                                endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemTopOrLeftSpace - this.itemSpace, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace)
                                rect = cc.Rect.fromMinMax(startPos, endPos)
                            }
                        } else if (item.isvIndex === this._totalCount - 1) {
                            if (this.isHorizontal) {
                                startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemSpace * 0.5, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace * 0.5)
                                endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemBottomOrRightSpace - this.itemSpace, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace)
                                rect = cc.Rect.fromMinMax(startPos, endPos)
                            } else {
                                startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemBottomOrRightSpace + this.itemSpace, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace)
                                endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemSpace * 0.5, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace * 0.5)
                                rect = cc.Rect.fromMinMax(startPos, endPos)
                            }
                        } else {
                            startPos = cc.v2(worldPos.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemSpace * 0.5, worldPos.y - item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this.itemSpace * 0.5)
                            endPos = cc.v2(worldPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemSpace * 0.5, worldPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemSpace * 0.5)
                            rect = cc.Rect.fromMinMax(startPos, endPos)
                        }
                        if (rect.contains(cc.v2(targetPos.x, targetPos.y))) {
                            noContains = false
                            doPage(item)

                            break
                        }
                    }
                    if (noContains) {
                        let item = this._lastPagedItem
                        item && doPage(item)
                    }
                }
                switch (this._pageExtraMode) {
                    case `Scale`:
                        let tempItems = []
                        this._items.forEach(item => {
                            let targetPos = this.node.convertToWorldSpaceAR(this.ndCenterPos.position),
                                targetDis: number,
                                pos = this.ndSvCtn.convertToWorldSpaceAR(item.position),
                                scale: number
                            if (this.isHorizontal) {
                                targetDis = Math.abs(targetPos.x - pos.x)
                                scale = (this.node.width * 0.5 - targetDis) / this.node.width / 0.5 * (this._maxPageScale - this._minPageScale) + this._minPageScale
                            } else {
                                targetDis = Math.abs(targetPos.y - pos.y)
                                scale = (this.node.height * 0.5 - targetDis) / this.node.height / 0.5 * (this._maxPageScale - this._minPageScale) + this._minPageScale
                            }
                            item.scale = scale
                            if (tempItems.length === 0) {
                                tempItems.push(item)
                            } else {
                                for (let index = 0; index < tempItems.length; index++) {
                                    let tempItem = tempItems[index]
                                    if (item.scale <= tempItem.scale || index === tempItems.length - 1) {
                                        tempItems.splice(index, 0, item)

                                        break
                                    }
                                }
                            }
                        })
                        tempItems.forEach((tempItem, index) => {
                            tempItem.zIndex = index
                        })

                        break

                    default:
                        break
                }

                break

            default:
                break
        }
        
        this._checkWidget(true)
    }

    /**
     * 滑动事件回调
     * @param sender 
     * @param event 
     */
    scrollEvent(sender: unknown, event: number) {
        // cc.log(`${cc.js.getClassName(this)} scrollEvent event = ${event}`)

        switch (event) {
            case 0:
                // cc.log("Scroll to Top")
                break
            case 1:
                // cc.log("Scroll to Bottom")
                break
            case 2:
                // cc.log("Scroll to Left")
                break
            case 3:
                // cc.log("Scroll to Right")
                break
            case 4:
                // cc.log("Scrolling")
                break
            case 5:
                // cc.log("Bounce Top")

                this._onBounceTop && this._onBounceTop()

                break
            case 6:
                // cc.log("Bounce bottom")
                this._onBounceBottom && this._onBounceBottom()

                break
            case 7:
                // cc.log("Bounce left")
                break
            case 8:
                // cc.log("Bounce right")
                break
            case 9:
                // cc.log("Auto scroll ended")

                this._scrollBegan = false
                switch (this._mode) {
                    case `Select`:
                        if (this._selecting && this._selectCall) {
                            this.scrollView.stopAutoScroll()
                            this._lastFinalSelectedIndex = this._lastSelectedItem.isvIndex
                            this._selectCall(this._lastSelectedItem, this._lastFinalSelectedIndex, true, true)
                            this.update(0)
                        }

                        break
                    case `Page`:
                        if (this._paging && this._pageCall) {
                            this.scrollView.stopAutoScroll()
                            this._pageCall(this._getItemByIndex(this._lastFinalPagedIndex), this._lastFinalPagedIndex, true, true)
                            this.update(0)
                        }

                        break

                    default:

                        break
                }

                break
            case 10:
                // cc.log(`${cc.js.getClassName(this)} onTouchUp`)

                switch (this._mode) {
                    case `Select`:
                        if (!this._scrollBegan) {
                            let item = this._getItemByIndex(this._lastFinalSelectedIndex)
                            if (item) {
                                this.scrollView.stopAutoScroll()
                                this._selecting = true
                                this.scrollToItem(item, SelcetActionTime)
                            }
                        }

                        break
                    // case cc.InfiniteScrollViewCpnt.Mode.Page:
                    //     if (!this._scrollBegan) {
                    //         let item = this._getItemByIndex(this._lastFinalPagedIndex)
                    //         if (item) {
                    //             this.scrollView.stopAutoScroll()
                    //             this._paging = true
                    //             this.scrollToItem(item, PageActionTime)
                    //         }
                    //     }

                    //     break

                    default:

                        break
                }

                break
            case 11:
                // cc.log("scroll ended with threshold")
                break
            case 12:
                // cc.log(`${cc.js.getClassName(this)} onScrollBegan`)

                this._scrollBegan = true
                this._selecting = false
                this._paging = false
                this.scrollView.stopAutoScroll()
                this._onScrollBegan && this._onScrollBegan()

                break
        }
    }

    // ====================================================================================
    // ===================================== 内部接口 ======================================
    // ====================================================================================

    /**
     * 设置是否横向滑动
     * @param isHorizontal 是否横向滑动
     */
    private _setHorizontal(isHorizontal: boolean) {
        cc.log(`${cc.js.getClassName(this)} _setHorizontal isHorizontal = ${isHorizontal}`)

        this.isHorizontal = isHorizontal
        this.isVertical = !this.isHorizontal
        this.scrollView.horizontal = this.isHorizontal
        this.scrollView.vertical = this.isVertical
        this._theoreticalMaxItemSize = this.isHorizontal ? cc.Canvas.instance.node.width : cc.Canvas.instance.node.height
        this._buffer = this._theoreticalMaxItemSize * (BufferAreaCoefficient + 0.5)
        this.ndSvCtn.anchorX = 0
        this.ndSvCtn.anchorY = 1
        this.wdgtSvCtn.top = 0
        this.wdgtSvCtn.bottom = 0
        this.wdgtSvCtn.left = 0
        this.wdgtSvCtn.right = 0
        if (this.isHorizontal) {
            this.wdgtSvCtn.isAlignTop = true
            this.wdgtSvCtn.isAlignBottom = true
            this.wdgtSvCtn.isAlignLeft = false
            this.wdgtSvCtn.isAlignRight = false
        } else {
            this.wdgtSvCtn.isAlignTop = false
            this.wdgtSvCtn.isAlignBottom = false
            this.wdgtSvCtn.isAlignLeft = true
            this.wdgtSvCtn.isAlignRight = true
        }
        this.wdgtSvCtn.updateAlignment()
    }

    /**
     * 检查适配
     * @param isUpdate 是否update中调用
     */
    private _checkWidget(isUpdate = false) {
        // cc.log(`${cc.js.getClassName(this)} _checkWidget`)

        switch (this._mode) {
            case `Select`:
                if (this.isHorizontal) {
                    this.ndSvView.width = 1
                    this.ndSvView.height = this.scrollView.node.height
                    this.wdgtSvView.right = this.scrollView.node.width * 0.5
                    this.wdgtSvView.bottom = 0
                } else {
                    this.ndSvView.width = this.scrollView.node.width
                    this.ndSvView.height = 1
                    this.wdgtSvView.right = 0
                    this.wdgtSvView.bottom = this.scrollView.node.height * 0.5
                }

                break
            case `Page`:
                if (this.isHorizontal) {
                    this.ndSvView.width = 1
                    this.ndSvView.height = this.scrollView.node.height
                    this.wdgtSvView.right = this.scrollView.node.width * 0.5
                    this.wdgtSvView.bottom = 0
                } else {
                    this.ndSvView.width = this.scrollView.node.width
                    this.ndSvView.height = 1
                    this.wdgtSvView.right = 0
                    this.wdgtSvView.bottom = this.scrollView.node.height * 0.5
                }

                break

            default:
                if (this._startFillPosReverse) {
                    if (this.isHorizontal) {
                        this.wdgtSvView.isAlignTop = true
                        this.wdgtSvView.isAlignBottom = true
                        this.wdgtSvView.isAlignLeft = false
                        this.wdgtSvView.isAlignRight = true
                        this.ndSvView.height = this.scrollView.node.height
                        let tarWidth = Math.min(this._curItemsSize, this.scrollView.node.width)
                        if (this.ndSvView.width !== tarWidth && !this._ndSvViewActing) {
                            this._ndSvViewActing = true
                            cc.tween(this.ndSvView)
                                .to(StartFillItemActTime, { width: tarWidth }, { easing: `quintOut` })
                                .call(() => {
                                    this.ndSvView.width = tarWidth
                                    this._ndSvViewActing = false
                                })
                                .start()
                        }
                    } else {
                        this.wdgtSvView.isAlignTop = false
                        this.wdgtSvView.isAlignBottom = true
                        this.wdgtSvView.isAlignLeft = true
                        this.wdgtSvView.isAlignRight = true
                        let tarHeight = Math.min(this._curItemsSize, this.scrollView.node.height)
                        this.ndSvView.width = this.scrollView.node.width
                        if (this.ndSvView.height !== tarHeight && !this._ndSvViewActing) {
                            this._ndSvViewActing = true
                            cc.tween(this.ndSvView)
                                .to(StartFillItemActTime, { height: tarHeight }, { easing: `quintOut` })
                                .call(() => {
                                    this.ndSvView.height = tarHeight
                                    this._ndSvViewActing = false
                                })
                                .start()
                        }
                    }
                } else {
                    if (this.isHorizontal) {
                        this.wdgtSvView.isAlignTop = true
                        this.wdgtSvView.isAlignBottom = true
                        this.wdgtSvView.isAlignLeft = true
                        this.wdgtSvView.isAlignRight = false
                        this.ndSvView.height = this.scrollView.node.height
                        this.ndSvView.width = this._totalCount === 0 ? this.scrollView.node.width : Math.min(this._curItemsSize, this.scrollView.node.width)
                    } else {
                        this.wdgtSvView.isAlignTop = true
                        this.wdgtSvView.isAlignBottom = false
                        this.wdgtSvView.isAlignLeft = true
                        this.wdgtSvView.isAlignRight = true
                        this.ndSvView.height = this._totalCount === 0 ? this.scrollView.node.height : Math.min(this._curItemsSize, this.scrollView.node.height)
                        this.ndSvView.width = this.scrollView.node.width
                    }
                }
                this.wdgtSvView.top = 0
                this.wdgtSvView.bottom = 0
                this.wdgtSvView.left = 0
                this.wdgtSvView.right = 0

                break
        }
        // update中不能立即调用updateAlignment，会引起嵌套父级scroll异常
        !isUpdate && this.wdgtSvView.updateAlignment()
    }

    private _createCall(_startIndex: number, isIniting: boolean): IsvItemObj {
        throw new Error("Method not implemented.");
    }

    private _activeCall(item: cc.Node, index: number, isActive: boolean, isIniting: boolean) {
        throw new Error("Method not implemented.");
    }

    /**
     * 列表上方添加节点
     * @param num 强制添加节点数量，无视缓冲区限制
     */
    private _addTopItems(num: number = 0): number {
        // cc.log(`${cc.js.getClassName(this)} _addTopItems`)

        let addCnt = 0
        for (let index = 0; index < this._totalCount; index++) {
            let boaderItem = this._items[0]
            if ((num > 0 || !boaderItem || this._getPositionInView(boaderItem).y + boaderItem.height * (this.affectedByScale ? boaderItem.scaleY : 1) * (this.affectedByScale ? boaderItem.scaleY : 1) * (1 - boaderItem.anchorY) - this._buffer < 0) && this._startIndex > 0) {
                if (this._startIndex > 0) {
                    addCnt++
                    num--
                    this._startIndex--
                    let itemObj: IsvItemObj,
                        item: IsvItem
                    itemObj = this._createCall(this._startIndex, false) as IsvItemObj
                    if (this._tempFinalPageItem) {
                        if (this._tempFinalPageItem.poolName === itemObj.poolName) {
                            item = this._tempFinalPageItem
                            this._tempFinalPageItem = null
                        } else {
                            let tempFinalPageItem = this._tempFinalPageItem
                            this._tempFinalPageItem = null
                            this._putItems([tempFinalPageItem])
                        }
                    } else {
                        item = itemObj.item as IsvItem
                        !item.parent && this.ndSvCtn.addChild(item)
                    }
                    item.poolName = itemObj.poolName
                    if (itemObj.static) {
                        this._statics[item.poolName] = item
                    }
                    item.isvIndex = this._startIndex
                    let widget = item.getComponent(cc.Widget)
                    widget && widget.updateAlignment();
                    (!this._statics[item.poolName] || this._isIniting) && this._activeCall(item, this._startIndex, true, this._isIniting)
                    item.y = this._items[0].y + this._items[0].height * (this.affectedByScale ? this._items[0].scaleY : 1) * (1 - this._items[0].anchorY) + this.itemSpace + item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY
                    this._items.unshift(item)
                    this._curItemsSize += item.height * (this.affectedByScale ? item.scaleY : 1) + this.itemSpace
                    if (item.y > 0) {
                        let offset = item.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) + this.itemTopOrLeftSpace
                        this.ndSvCtn.height += offset
                        this.ndSvCtn.y += offset
                        this.ndSvCtn.children.forEach(child => {
                            child.y -= offset
                        })
                    }
                    let viewPos = this._getPositionInView(item)
                    if (this._startIndex === 0) {
                        break
                    }
                    let topOffset = viewPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) - this._buffer
                    if (topOffset >= 0 && num <= 0) {
                        break
                    }
                } else {
                    if (this.scrollView.isScrolling()) {
                        let item = this._items[0]
                        let offset = -item.y - item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) - this.itemTopOrLeftSpace
                        this.ndSvCtn.children.forEach(child => {
                            child.y += offset
                        })
                        this.ndSvCtn.y -= offset
                        this.ndSvCtn.height -= offset
                    }
                }
            } else {
                break
            }
        }

        this._checkWidget()

        return addCnt
    }

    /**
     * 列表下方添加节点
     * @param num 强制添加节点数量，无视缓冲区限制
     */
    private _addBottomItems(num: number = 0): number {
        // cc.log(`${cc.js.getClassName(this)} _addBottomItems`)

        let addCnt = 0
        for (let index = 0; index < this._totalCount; index++) {
            // cc.log(`${cc.js.getClassName(this)} this._totalCount = ${this._totalCount} this._endIndex = ${this._endIndex}`)
            let boaderItem = this._items[this._items.length - 1]
            if ((num > 0 || !boaderItem || -this._getPositionInView(boaderItem).y + boaderItem.height * (this.affectedByScale ? boaderItem.scaleY : 1) * boaderItem.anchorY - this._buffer < 0) && this._endIndex < this._totalCount - 1) {
                addCnt++
                num--
                this._endIndex++
                let itemObj: IsvItemObj,
                    item: IsvItem
                itemObj = this._createCall(this._endIndex, false) as IsvItemObj
                if (this._tempFinalPageItem) {
                    if (this._tempFinalPageItem.poolName === itemObj.poolName) {
                        item = this._tempFinalPageItem
                        this._tempFinalPageItem = null
                    } else {
                        let tempFinalPageItem = this._tempFinalPageItem
                        this._tempFinalPageItem = null
                        this._putItems([tempFinalPageItem])
                    }
                } else {
                    item = itemObj.item as IsvItem
                    !item.parent && this.ndSvCtn.addChild(item)
                }
                item.poolName = itemObj.poolName
                if (itemObj.static) {
                    this._statics[item.poolName] = item
                }
                item.isvIndex = this._endIndex
                let widget = item.getComponent(cc.Widget)
                widget && widget.updateAlignment();
                (!this._statics[item.poolName] || this._isIniting) && this._activeCall(item, this._endIndex, true, this._isIniting)
                let endItem = this._items[this._items.length - 1]
                item.y = (endItem ? endItem.y - endItem.height * (this.affectedByScale ? endItem.scaleY : 1) * endItem.anchorY : -this.itemTopOrLeftSpace + this.itemSpace) - this.itemSpace - item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY)
                this.ndSvCtn.height = -item.y + item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY + this.itemBottomOrRightSpace
                this._items.push(item)
                this._curItemsSize += item.height * (this.affectedByScale ? item.scaleY : 1) + this.itemSpace
                if (this._startIndex === 0 && item === this._items[0]) {
                    let offset = -item.y - item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY) - this.itemTopOrLeftSpace
                    offset > 0 && this.ndSvCtn.children.forEach(child => {
                        child.y += offset
                    })
                }
                if (this._endIndex === this._totalCount - 1) {

                    break
                }
                let viewPos = this._getPositionInView(item),
                    bottomOffset = -viewPos.y + item.height * (this.affectedByScale ? item.scaleY : 1) * item.anchorY - this._buffer
                if (bottomOffset >= 0 && num <= 0) {

                    break
                }
            } else {
                break
            }
        }

        this._checkWidget()

        return addCnt
    }

    /**
     * 列表左方添加节点
     * @param num 强制添加节点数量，无视缓冲区限制
     */
    private _addLeftItems(num: number = 0): number {
        // cc.log(`${cc.js.getClassName(this)} _addLeftItems`)

        let addCnt = 0
        for (let index = 0; index < this._totalCount; index++) {
            let boaderItem = this._items[0]
            if ((num > 0 || !boaderItem || -this._getPositionInView(boaderItem).x + boaderItem.width * (this.affectedByScale ? boaderItem.scaleY : 1) * boaderItem.anchorX - this._buffer < 0) && this._startIndex > 0) {
                if (this._startIndex > 0) {
                    addCnt++
                    num--
                    this._startIndex--
                    let itemObj: IsvItemObj,
                        item: IsvItem
                    itemObj = this._createCall(this._startIndex, false) as IsvItemObj
                    if (this._tempFinalPageItem) {
                        if (this._tempFinalPageItem.poolName === itemObj.poolName) {
                            item = this._tempFinalPageItem
                            this._tempFinalPageItem = null
                        } else {
                            let tempFinalPageItem = this._tempFinalPageItem
                            this._tempFinalPageItem = null
                            this._putItems([tempFinalPageItem])
                        }
                    } else {
                        item = itemObj.item as IsvItem
                        !item.parent && this.ndSvCtn.addChild(item)
                    }
                    item.poolName = itemObj.poolName
                    if (itemObj.static) {
                        this._statics[item.poolName] = item
                    }
                    item.isvIndex = this._startIndex
                    let widget = item.getComponent(cc.Widget)
                    widget && widget.updateAlignment();
                    (!this._statics[item.poolName] || this._isIniting) && this._activeCall(item, this._startIndex, true, this._isIniting)
                    item.x = this._items[0].x - this._items[0].width * (this.affectedByScale ? this._items[0].scaleX : 1) * this._items[0].anchorX - this.itemSpace - item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX)
                    this._items.unshift(item)
                    this._curItemsSize += item.width * (this.affectedByScale ? item.scaleX : 1) + this.itemSpace
                    if (item.x < 0) {
                        let offset = -item.x + item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX + this.itemTopOrLeftSpace
                        this.ndSvCtn.width += offset
                        this.ndSvCtn.x -= offset
                        this.ndSvCtn.children.forEach(child => {
                            child.x += offset
                        })
                    }
                    let viewPos = this._getPositionInView(item)
                    if (this._startIndex === 0) {
                        break
                    }
                    let leftOffset = -viewPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this._buffer
                    if (leftOffset >= 0 && num <= 0) {
                        break
                    }
                } else {
                    if (this.scrollView.isScrolling()) {
                        let item = this._items[0]
                        let offset = item.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemTopOrLeftSpace
                        this.ndSvCtn.children.forEach(child => {
                            child.x -= offset
                        })
                        this.ndSvCtn.x -= offset
                        this.ndSvCtn.width -= offset
                    }
                }
            } else {
                break
            }
        }

        this._checkWidget()

        return addCnt
    }

    /**
     * 列表右方添加节点
     * @param num 强制添加节点数量，无视缓冲区限制
     */
    private _addRightItems(num: number = 0): number {
        // cc.log(`${cc.js.getClassName(this)} _addRightItems`)

        let addCnt = 0
        for (let index = 0; index < this._totalCount; index++) {
            let boaderItem = this._items[this._items.length - 1]
            if ((num > 0 || !boaderItem || this._getPositionInView(boaderItem).x + boaderItem.width * (this.affectedByScale ? boaderItem.scaleY : 1) * (1 - boaderItem.anchorX) - this._buffer < 0) && this._endIndex < this._totalCount - 1) {
                addCnt++
                num--
                this._endIndex++
                let itemObj: IsvItemObj,
                    item: IsvItem
                itemObj = this._createCall(this._endIndex, false) as IsvItemObj
                if (this._tempFinalPageItem) {
                    if (this._tempFinalPageItem.poolName === itemObj.poolName) {
                        item = this._tempFinalPageItem
                        this._tempFinalPageItem = null
                    } else {
                        let tempFinalPageItem = this._tempFinalPageItem
                        this._tempFinalPageItem = null
                        this._putItems([tempFinalPageItem])
                    }
                } else {
                    item = itemObj.item as IsvItem
                    !item.parent && this.ndSvCtn.addChild(item)
                }
                item.poolName = itemObj.poolName
                if (itemObj.static) {
                    this._statics[item.poolName] = item
                }
                item.isvIndex = this._endIndex
                let widget = item.getComponent(cc.Widget)
                widget && widget.updateAlignment();
                (!this._statics[item.poolName] || this._isIniting) && this._activeCall(item, this._endIndex, true, this._isIniting)
                let endItem = this._items[this._items.length - 1]
                item.x = (endItem ? endItem.x + endItem.width * (this.affectedByScale ? endItem.scaleX : 1) * (1 - endItem.anchorX) : this.itemTopOrLeftSpace - this.itemSpace) + this.itemSpace + item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX
                this.ndSvCtn.width = item.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) + this.itemBottomOrRightSpace
                this._items.push(item)
                this._curItemsSize += item.width * (this.affectedByScale ? item.scaleX : 1) + this.itemSpace
                if (this._startIndex === 0 && item === this._items[0]) {
                    let offset = item.x - item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX - this.itemTopOrLeftSpace
                    offset > 0 && this.ndSvCtn.children.forEach(child => {
                        child.x -= offset
                    })
                }
                if (this._endIndex === this._totalCount - 1) {

                    break
                }
                let viewPos = this._getPositionInView(item),
                    rightOffset = viewPos.x + item.width * (this.affectedByScale ? item.scaleX : 1) * (1 - item.anchorX) - this._buffer
                if (rightOffset >= 0 && num <= 0) {

                    break
                }
            } else {
                break
            }
        }

        this._checkWidget()

        return addCnt
    }

    /**
     * 获取目标列表节点的本地位置
     * @param item 列表节点
     */
    private _getPositionInView(item: cc.Node & { poolName?: string; isvIndex: number; }) {
        // cc.log(`${cc.js.getClassName(this)} _getPositionInView`)

        let worldPos = item.parent.convertToWorldSpaceAR(item.position)
        let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos)
        return viewPos
    }

    /**
     * 回收节点
     * @param putItems 要回收的节点
     */
    private _putItems(putItems: (cc.Node & { poolName?: string; isvIndex: number; })[]) {
        // cc.log(`${cc.js.getClassName(this)} _putItems`)

        putItems = JKManager.Utils.copyObj(putItems)
        putItems.forEach((putItem, index) => {
            if (putItem !== this._tempFinalPageItem) {
                let putItemIndex = this._items.indexOf(putItem)
                this._items.splice(putItemIndex, 1)
                if (this._statics[putItem.poolName]) {
                    if (this.isHorizontal) {
                        this._statics[putItem.poolName].y = 50000
                    } else {
                        this._statics[putItem.poolName].x = 50000
                    }
                } else {
                    this._nodePools[putItem.poolName].put(putItem)
                }
                this._curItemsSize -= (this.isHorizontal ? putItem.width * (this.affectedByScale ? putItem.scaleX : 1) : putItem.height * (this.affectedByScale ? putItem.scaleY : 1)) + this.itemSpace
            } else {
                let putItemIndex = this._items.indexOf(putItem)
                this._items.splice(putItemIndex, 1)
                this._curItemsSize -= (this.isHorizontal ? putItem.width * (this.affectedByScale ? putItem.scaleX : 1) : putItem.height * (this.affectedByScale ? putItem.scaleY : 1)) + this.itemSpace
            }
            !this._statics[putItem.poolName] && this._activeCall(putItem, putItem.isvIndex, false, this._isIniting)
        })
    }

    /**
     * 通过序号获取节点
     * @param tarIndex 序号
     */
    private _getItemByIndex(tarIndex: number) {
        // cc.log(`${cc.js.getClassName(this)} _getItemByIndex index = ${index}`)

        let tarItem: cc.Node & { poolName?: string; isvIndex: number; }
        for (let index = 0; index < this._items.length; index++) {
            let item = this._items[index]
            if (item.isvIndex === tarIndex) {
                tarItem = item

                break
            }
        }

        return tarItem
    }

    // ====================================================================================
    // ===================================== 外部接口 ======================================
    // ====================================================================================

    /**
     * 显示
     */
    show() {
        // cc.log(`${cc.js.getClassName(this)} show`)

        this._isShow = true
        this.node.active = this._isShow

        return this._isShow
    }

    /**
     * 隐藏
     */
    hide() {
        // cc.log(`${cc.js.getClassName(this)} hide`)

        this._isShow = false
        this.node.active = this._isShow

        return this._isShow
    }

    /**
     * @returns 当前礼物列表显示状态
     */
    isShow() {
        // cc.log(`${cc.js.getClassName(this)} isShow`)

        return this._isShow
    }

    /**
     * 初始化无限滑动列表
     * @param totalCount 列表节点数量
     * @param createCall 滑动节点创建回调 createCall(index 节点索引) return {item Node节点, poolName 节点池名称, isIniting 是否初始化的回调}
     * @param activeCall 进入或离开视窗的回调 activeCall(item 节点, index 节点索引, isActive 是否显示状态, isIniting 是否初始化的回调)
     * @param isHorizontal 是否横向滑动
     * @param resetPosHead 是否重置列表位置状态到第一个元素，默认false，为true时先清空旧节点再创建新节点，为false时，先创建新节点在清空旧节点
     * @example 
        scrollViewCpnt.init(this._listData.length, this._svItemInitCall.bind(this), this._svItemActiveCall.bind(this), false)
     */
    init(totalCount: number, createCall: (index: number, isIniting: boolean) => IsvItemObj, activeCall: (item: cc.Node, index: number, isActive: boolean, isIniting: boolean) => void, isHorizontal = this.isHorizontal, resetPosHead = false) {
        cc.log(`${cc.js.getClassName(this)} init`)

        this._isIniting = true
        this.scrollView.stopAutoScroll()
        this._totalCount = totalCount
        this._createCall = createCall
        this._activeCall = activeCall
        this._setHorizontal(isHorizontal)
        if (isHorizontal !== this.isHorizontal || this._curItemsSize === undefined || this._totalCount === 0) {
            resetPosHead = true
        }
        this._checkWidget()
        if (resetPosHead) {
            this._putItems(this._items)
            this._startIndex = 0
            this.ndSvCtn.x = 0
            this.ndSvCtn.y = 0
            this._lastContentPosX = this.ndSvCtn.x
            this._lastContentPosY = this.ndSvCtn.y
            this._endIndex = this._startIndex - 1
            this._curItemsSize = this.itemTopOrLeftSpace + this.itemBottomOrRightSpace - this.itemSpace * 2
            if (this.isHorizontal) {
                if (this._totalCount > 0) {
                    this.ndSvCtn.width = this._curItemsSize
                    this._addRightItems()
                    this._addLeftItems()
                } else {
                    this.ndSvCtn.width = this.node.width
                }
            } else {
                if (this._totalCount > 0) {
                    this.ndSvCtn.height = this._curItemsSize
                    this._addBottomItems()
                    this._addTopItems()
                } else {
                    this.ndSvCtn.height = this.node.height
                }
            }
            this.scrollToIndex(this._startIndex)
        } else {
            let offset = this.getScrollOffset()
            this._putItems(this._items)
            this._startIndex = 0
            this.ndSvCtn.x = 0
            this.ndSvCtn.y = 0
            this._lastContentPosX = this.ndSvCtn.x
            this._lastContentPosY = this.ndSvCtn.y
            this._endIndex = this._startIndex - 1
            this._curItemsSize = this.itemTopOrLeftSpace + this.itemBottomOrRightSpace - this.itemSpace * 2
            if (this.isHorizontal) {
                if (this._totalCount > 0) {
                    this.ndSvCtn.width = this._curItemsSize
                    this._addRightItems()
                    this._addLeftItems()
                } else {
                    this.ndSvCtn.width = this.node.width
                }
            } else {
                if (this._totalCount > 0) {
                    this.ndSvCtn.height = this._curItemsSize
                    this._addBottomItems()
                    this._addTopItems()
                } else {
                    this.ndSvCtn.height = this.node.height
                }
            }
            this.scrollToOffset(offset)
            let lastSelectedIndex = this._lastSelectedItem ? this._lastSelectedItem.isvIndex : -1
            if (lastSelectedIndex >= 0) {
                if (lastSelectedIndex < this._totalCount) {
                    this.scrollToItem(this._getItemByIndex(lastSelectedIndex))
                } else {
                    this.scrollToItem(this._getItemByIndex(this._totalCount - 1))
                }
            }
        }
        this._isIniting = false
    }

    /**
     * 插入列表项
     * @param index 插入位置序号
     * @param numOrItems 数量或者需要直接插入的节点（直接插入不会触发刷新回调）
     */
    insertItem(index: number, numOrItems: number | cc.Node[]) {
        cc.log(`${cc.js.getClassName(this)} insertItem index = ${index} numOrItems = ${numOrItems}`)

        if (index >= 0 && numOrItems) {
            this.stopAutoScroll()
            let tarIndex = this._items.length
            for (let i = 0; i < this._items.length; i++) {
                let item = this._items[i],
                    deleteActCall = item.deleteActCall,
                    insertActCall = item.insertActCall
                item.stopAllActions()
                deleteActCall && deleteActCall()
                insertActCall && insertActCall()
                if (index === item.isvIndex) {
                    tarIndex = i
                }
            }
            let insertItems: IsvItem[] = [],
                num: number
            if (numOrItems instanceof Array) {
                if (numOrItems.length > 0) {
                    insertItems = numOrItems as IsvItem[]
                    num = insertItems.length
                }
            } else {
                if (numOrItems > 0) {
                    num = numOrItems
                }
            }
            if (num) {
                this._totalCount += num
            }
            let tarOffset = cc.v3()
            for (let j = 0; j < num; j++) {
                this._endIndex++
                let itemObj: IsvItemObj,
                    item = insertItems[j],
                    worldOPos: cc.Vec3
                if (item) {
                    worldOPos = cc.v3(item.parent.convertToWorldSpaceAR(item.getPosition()))
                } else {
                    itemObj = this._createCall(index + j, false) as IsvItemObj
                    item = itemObj.item as IsvItem
                    item.poolName = itemObj.poolName
                    if (itemObj.static) {
                        this._statics[item.poolName] = item
                    }
                }
                if (this.ndSvCtn !== item.parent) {
                    item.parent = null
                    !item.parent && this.ndSvCtn.addChild(item)
                }
                item.isvIndex = index + j
                if (insertItems.length) {
                    item.position = this.ndSvCtn.convertToNodeSpaceAR(worldOPos)
                } else {
                    let widget = item.getComponent(cc.Widget)
                    widget && widget.updateAlignment()
                }
                (!this._statics[item.poolName] || this._isIniting) && this._activeCall(item, item.isvIndex, true, this._isIniting)
                let endItem = this._items[tarIndex - 1 + j],
                    tarPos = cc.v3()
                if (this.isHorizontal) {
                    tarPos.y = -this.ndSvCtn.height * 0.5 + (0.5 - item.anchorY) * item.height
                    tarPos.x = (endItem ? endItem.x + endItem.width * (this.affectedByScale ? endItem.scaleX : 1) * (1 - endItem.anchorX) : this.itemTopOrLeftSpace - this.itemSpace) + this.itemSpace + item.width * (this.affectedByScale ? item.scaleX : 1) * item.anchorX
                    let offset = item.width * (this.affectedByScale ? item.scaleX : 1) + this.itemSpace
                    this.ndSvCtn.width += offset
                    this._curItemsSize += offset
                    tarOffset.x += offset
                } else {
                    tarPos.x = this.ndSvCtn.width * 0.5 + (item.anchorX - 0.5) * item.width
                    tarPos.y = (endItem ? endItem.y - endItem.height * (this.affectedByScale ? endItem.scaleY : 1) * endItem.anchorY : -this.itemTopOrLeftSpace + this.itemSpace) - this.itemSpace - item.height * (this.affectedByScale ? item.scaleY : 1) * (1 - item.anchorY)
                    let offset = item.height * (this.affectedByScale ? item.scaleY : 1) + this.itemSpace
                    this.ndSvCtn.height += offset
                    this._curItemsSize += offset
                    tarOffset.y += offset
                }
                if (insertItems.length) {
                    cc.tween(item)
                        .to(InsertOrDeleteItemActTime, { position: tarPos })
                        .call(() => {
                            item.position = tarPos
                        })
                        .start()
                } else {
                    item.position = tarPos
                }
                this._items.splice(tarIndex + j, 0, item)
            }
            for (let k = tarIndex + num; k < this._items.length; k++) {
                let item = this._items[k]
                item.isvIndex += num
                let oPos = cc.v3(item.x, item.y),
                    tarPos = cc.v3(oPos.x + tarOffset.x, oPos.y + tarOffset.y);
                item.insertActCall = () => {
                    item.position = tarPos
                    item.insertActCall = null
                }
                cc.tween(item)
                    .to(InsertOrDeleteItemActTime, { position: tarPos })
                    .call(() => {
                        item.insertActCall()
                    })
                    .start();
                (!this._statics[item.poolName] || this._isIniting) && this._activeCall(item, item.isvIndex, true, this._isIniting)
            }
        }
    }

    /**
     * 删除列表项
     * @param index 删除位置序号
     * @param num 数量
     * @param isOutOfCtrl 是否脱离滑动组件控制，脱离控制不会触发刷新回调且只支持删除存在的列表项，默认否
     */
    deleteItem(index: number, num: number, isOutOfCtrl = false) {
        cc.log(`${cc.js.getClassName(this)} deleteItem index = ${index} num = ${num}`)

        if (index >= 0 && num > 0) {
            this.stopAutoScroll()
            this._totalCount -= num
            let tarIndex = -1,
                deleteNum = 0,
                offset = 0
            for (let i = 0; i < this._items.length; i++) {
                let item = this._items[i],
                    deleteActCall = item.deleteActCall,
                    insertActCall = item.insertActCall
                item.stopAllActions()
                deleteActCall && deleteActCall()
                insertActCall && insertActCall()
                if (item.isvIndex === index) {
                    tarIndex = i
                }
                if (tarIndex >= 0) {
                    if (num > 0) {
                        if (this.isHorizontal) {
                            offset -= item.width * (this.affectedByScale ? item.scaleX : 1) + this.itemSpace
                        } else {
                            offset -= item.height * (this.affectedByScale ? item.scaleY : 1) + this.itemSpace
                        }
                        item.parent = null
                        num--
                        deleteNum++
                    }
                }
            }
            let tarOffset = this.isHorizontal ? cc.v3(offset) : cc.v3(0, offset),
                realAddNum = 0
            if (isOutOfCtrl) {
                this._items.splice(tarIndex, deleteNum)
                this._endIndex -= deleteNum
                if (this.isHorizontal) {
                    realAddNum = this._addRightItems(deleteNum)
                    this.ndSvCtn.width += tarOffset.x
                    this._curItemsSize += tarOffset.x
                } else {
                    realAddNum = this._addBottomItems(deleteNum)
                    this.ndSvCtn.height += tarOffset.y
                    this._curItemsSize += tarOffset.y
                }
            } else {
                this._putItems(this._items.slice(tarIndex, deleteNum))
                if (this.isHorizontal) {
                    realAddNum = this._addRightItems(deleteNum)
                    this.ndSvCtn.width += tarOffset.x
                } else {
                    realAddNum = this._addBottomItems(deleteNum)
                    this.ndSvCtn.height += tarOffset.y
                }
            }
            let cnt = tarIndex
            for (let j = tarIndex; j < this._items.length; j++) {
                let item = this._items[j]
                if (cnt < this._items.length - realAddNum) {
                    item.isvIndex -= deleteNum
                }
                cnt++
                let oPos = cc.v3(item.x, item.y),
                    tarPos = cc.v3(oPos.x + tarOffset.x, oPos.y + tarOffset.y);
                item.deleteActCall = () => {
                    item.position = tarPos
                    item.deleteActCall = null
                }
                cc.tween(item)
                    .to(InsertOrDeleteItemActTime, { position: tarPos })
                    .call(() => {
                        item.deleteActCall()
                    })
                    .start();
                (!this._statics[item.poolName] || this._isIniting) && this._activeCall(item, item.isvIndex, true, this._isIniting)
            }
            if (this.isHorizontal) {
                this._addLeftItems()
            } else {
                this._addTopItems()
            }
        }
    }

    /**
     * 头部追加列表项
     * @param num 数量
     */
    unShiftItem(num: number) {
        cc.log(`${cc.js.getClassName(this)} unShiftItem num = ${num}`)

        this.insertItem(0, num)
    }

    /**
     * 删除头部列表项
     * @param num 数量
     */
    shiftItem(num: number) {
        cc.log(`${cc.js.getClassName(this)} shiftItem num = ${num}`)

        this.deleteItem(0, num)
    }

    /**
     * 尾部追加列表项
     * @param num 数量
     */
    pushItem(num: number) {
        cc.log(`${cc.js.getClassName(this)} pushItem num = ${num}`)

        this.insertItem(this._totalCount, num)
    }

    /**
     * 删除尾部列表项
     * @param num 数量
     */
    popItem(num: number) {
        cc.log(`${cc.js.getClassName(this)} popItem num = ${num}`)

        this.deleteItem(this._totalCount - 1, num)
    }

    /**
     * 通过节点池名称从节点池获取节点(如果没有，则需要自行创建，此工具只是代为管理节点池)
     * @param poolName 节点池名称
     */
    getItemFromPoolWithName(poolName: string) {
        // cc.log(`${cc.js.getClassName(this)} getItemFromPoolWithName poolName = ${poolName}`)

        let node = null
        this._nodePools[poolName] = this._nodePools[poolName] || new cc.NodePool()
        let nodePool = this._nodePools[poolName]
        node = this._statics[poolName] || nodePool.get()

        return node
    }

    /**
     * 滑动到指定偏移量
     * @param offset 偏移量
     * @param time 时间
     */
    scrollToOffset(offset: cc.Vec2, time = 0) {
        // cc.log(`${cc.js.getClassName(this)} scrollToItem offset = ${offset}`)

        this.scrollView.scrollToOffset(offset, time)
    }

    /**
     * 移动到指定偏移量，不会触发回弹
     * @param offset 偏移量
     * @param time 时间
     */
    moveToOffset(offset: cc.Vec2, time = 0) {
        // cc.log(`${cc.js.getClassName(this)} moveToOffset offset = ${offset}`)

        this.ndSvCtn.stopAllActions()
        if (this.isHorizontal) {
            let tarX = -offset.x - this.ndSvView.width * this.ndSvView.anchorX
            cc.tween(this.ndSvCtn)
                .to(time, { x: tarX })
                .start()
        } else {
            let tarY = offset.y + this.ndSvView.height * (1 - this.ndSvView.anchorY)
            cc.tween(this.ndSvCtn)
                .to(time, { y: tarY })
                .start()
        }
    }

    /**
     * 滑动到指定节点
     * @param item 节点
     * @param time 滑动时间
     */
    scrollToItem(item: cc.Node & { poolName?: string; isvIndex: number; }, time = 0) {
        // cc.log(`${cc.js.getClassName(this)} scrollToItem item.isvIndex = ${item.isvIndex}`)

        this.scrollView.stopAutoScroll()
        let targetPos = this.node.convertToWorldSpaceAR(this.ndCenterPos.position),
            targetDis: number,
            pos = this.ndSvCtn.convertToWorldSpaceAR(item.position)
        if (this.isHorizontal) {
            targetDis = targetPos.x - pos.x
            let offset = this.getScrollOffset(),
                targetOffset = cc.v2(-offset.x - targetDis, offset.y)
            this.scrollView.scrollToOffset(targetOffset, time)
        } else {
            targetDis = targetPos.y - pos.y
            let offset = this.getScrollOffset(),
                targetOffset = cc.v2(offset.x, offset.y + targetDis)
            this.scrollView.scrollToOffset(targetOffset, time)
        }
        if (Math.abs(targetDis) > 1e-4) {
            return true
        } else {
            return false
        }
    }

    /**
     * 滑动到指定序号节点
     * @param index 序号
     * @param time 滑动时间
     */
    scrollToIndex(index: number, time = 0) {
        // cc.log(`${cc.js.getClassName(this)} scrollToIndex index = ${index}`)

        if (this._totalCount > 0 && index >= 0) {
            index = index <= this._totalCount - 1 ? index : this._totalCount - 1
            this.scrollView.stopAutoScroll()
            let tarItem = this._getItemByIndex(index)
            this._lastFinalPagedIndex = index
            this._paging = true
            if (tarItem) {
                this.scrollToItem(tarItem, time)
            } else {
                this._tempFinalPageItem = this._getItemByIndex(this._lastFinalPagedIndex)
                this._putItems(this._items)
                this._startIndex = index
                this.ndSvCtn.x = 0
                this.ndSvCtn.y = 0
                this._lastContentPosX = this.ndSvCtn.x
                this._lastContentPosY = this.ndSvCtn.y
                this._endIndex = this._startIndex - 1
                this._curItemsSize = this.itemTopOrLeftSpace + this.itemBottomOrRightSpace - this.itemSpace * 2
                if (this.isHorizontal) {
                    if (this._totalCount > 0) {
                        this.ndSvCtn.width = this._curItemsSize
                        this._addRightItems()
                        this._addLeftItems()
                    } else {
                        this.ndSvCtn.width = this.node.width
                    }
                } else {
                    if (this._totalCount > 0) {
                        this.ndSvCtn.height = this._curItemsSize
                        this._addBottomItems()
                        this._addTopItems()
                    } else {
                        this.ndSvCtn.height = this.node.height
                    }
                }
                this.update(0)
                this.scrollToItem(this._getItemByIndex(this._lastFinalPagedIndex), time)
            }
            this._pageCall && this._pageCall(this._getItemByIndex(this._lastFinalPagedIndex), this._lastFinalPagedIndex, true, true)
        } else {
            this._putItems(this._items)
        }
    }

    /**
     * 设置当前页面索引
     * @param index 
     */
    setCurrentPageIndex(index: number) {
        cc.log(`${cc.js.getClassName(this)} setCurrentPageIndex index = ${index}`)

        this.scrollToIndex(index)
    }

    /**
     * 获取当前页面索引
     */
    getCurrentPageIndex() {
        // cc.log(`${cc.js.getClassName(this)} getCurrentPageIndex`)

        return this._lastFinalPagedIndex || 0
    }

    // 获取当前显示的第一项序号（包括缓冲区中的项）
    getStartIndex() {
        cc.log(`${cc.js.getClassName(this)} getStartIndex this._startIndex = ${this._startIndex}`)

        return this._startIndex
    }

    // 获取当前列表滑动偏移量
    getScrollOffset() {
        // cc.log(`${cc.js.getClassName(this)} getScrollOffset`)

        let scrollOffset = this.scrollView.getScrollOffset()

        return scrollOffset
    }

    // 获取所有当前显示的项（包括缓冲区中的项）
    getActiveItems() {
        // cc.log(`${cc.js.getClassName(this)} getActiveItems`)

        return JKManager.Utils.copyObj(this._items)
    }
    //获取屏幕中的所有显示项
    getItemsInScreen() {
        let res: IsvItem[] = [];
        let insertItems = this.getActiveItems();
        for (let item of insertItems) {
            let wPos = item.convertToWorldSpaceAR(cc.Vec2.ZERO);
            if (wPos.x >= 0 &&
                wPos.x <= cc.winSize.width &&
                wPos.y >= 0 &&
                wPos.y <= cc.winSize.height) {
                res.push(item);
            }
        }
        return res;
    }

    /**
     * 根据列表项获取序号
     * @param item 列表项
     */
    getIndexByItem(item: cc.Node & { isvIndex: number }) {
        cc.log(`${cc.js.getClassName(this)} getIndexByItem`)

        return item.isvIndex
    }

    /**
     * 设置列表项间隔
     * @param itemSpace 间隔
     * @param itemTopOrLeftSpace 首间隔
     * @param itemBottomOrRightSpace 尾间隔
     */
    setItemSpace(itemSpace: number, itemTopOrLeftSpace: number = 0, itemBottomOrRightSpace: number = 0) {
        cc.log(`${cc.js.getClassName(this)} setItemSpace itemSpace = ${itemSpace} itemTopOrLeftSpace = ${itemTopOrLeftSpace} itemBottomOrRightSpace = ${itemBottomOrRightSpace}`)

        this.itemSpace = itemSpace
        this.itemTopOrLeftSpace = itemTopOrLeftSpace || itemSpace
        this.itemBottomOrRightSpace = itemBottomOrRightSpace || itemSpace
    }

    /**
     * 设置滑动模式
     * @param mode 模式
     * @param call 回调
     */
    setMode(mode: IsvMode = `Nomal`, call: (item: cc.Node, index: number, isCurItem: boolean, isFinalItem: boolean) => void = null) {
        cc.log(`${cc.js.getClassName(this)} setMode mode = ${mode}`)

        this._mode = mode
        switch (this._mode) {
            case `Select`:
                this._selectCall = call
                this._lastSelectedItem = null
                // this._lastFinalSelectedIndex = 0

                break
            case `Page`:
                this.setPageExtraMode(this._pageExtraMode || `Nomal`, this._maxPageScale, this._minPageScale)
                this._pageCall = call
                this._lastPagedItem = null
                this._lastFinalPagedIndex = 0

                break
            case `Nomal`:
                this._selectCall = null
                this._pageCall = null

                break

            default:
                this._selectCall = null
                this._pageCall = null

                break
        }
    }

    /**
     * 设置轮播额外效果模式
     * @param pageExtraMode 模式，根据不同类型需要额外传入不同参数
     * @param maxPageScale 最大缩放比例，缩放模式下有效
     * @param minPageScale 最小缩放比例，缩放模式下有效
     * cc.InfiniteScrollViewCpnt.PageExtraMode.Nomal 无额外参数
     * cc.InfiniteScrollViewCpnt.PageExtraMode.Scale MaxPageScale 最大比例, MinPageScale 最小比例
     */
    setPageExtraMode(pageExtraMode: IsvPageExtraMode = `Nomal`, maxPageScale: number = 0, minPageScale: number = 0) {
        cc.log(`${cc.js.getClassName(this)} setPageExtraMode pageExtraMode = ${pageExtraMode}`)

        this._pageExtraMode = pageExtraMode
        switch (this._pageExtraMode) {
            case `Nomal`:
                break
            case `Scale`:
                this._maxPageScale = maxPageScale || this._maxPageScale
                this._minPageScale = minPageScale || this._minPageScale

                break

            default:

                break
        }
    }

    /**
     * 滑动到顶部
     * @param time 时间（任意方向偏移量不大于屏幕的情况）
     */
    scrollToTop(time = 0) {
        cc.log(`${cc.js.getClassName(this)} scrollToTop time = ${time}`)

        this.scrollView.scrollToTop(time)
    }

    /**
     * 滑动到底部
     * @param time 时间（任意方向偏移量不大于屏幕的情况）
     */
    scrollToBottom(time = 0) {
        cc.log(`${cc.js.getClassName(this)} scrollToBottom time = ${time}`)

        this.scrollView.scrollToBottom(time)
    }

    /**
     * 滑动到左侧
     * @param time 时间（任意方向偏移量不大于屏幕的情况）
     */
    scrollToLeft(time = 0) {
        cc.log(`${cc.js.getClassName(this)} scrollToLeft time = ${time}`)

        this.scrollView.scrollToLeft(time)
    }

    /**
     * 滑动到右侧
     * @param time 时间（任意方向偏移量不大于屏幕的情况）
     */
    scrollToRight(time = 0) {
        cc.log(`${cc.js.getClassName(this)} scrollToRight time = ${time}`)

        this.scrollView.scrollToRight(time)
    }

    /**
     * 获取列表项滑动方向尺寸（同步方法，需自行考虑时序）
     */
    getCtnHeightOrWidth() {
        // cc.log(`${cc.js.getClassName(this)} getCtnHeightOrWidth`)

        return this.isHorizontal ? this.ndSvCtn.width : this.ndSvCtn.height
    }

    /**
     * 是否启用组件自带遮罩组件
     * @param bool 是否启用
     */
    setMaskEnabled(bool: boolean) {
        cc.log(`${cc.js.getClassName(this)} setMaskEnabled bool = ${bool}`)

        this.node.getComponent(cc.Mask).enabled = bool
    }

    /**
     * 是否启用惯性
     * @param bool 是否启用
     * @param brake 开启惯性后，在用户停止触摸后滚动多快停止，0表示永不停止，1表示立刻停止
     */
    setInertia(bool: boolean, brake: number) {
        cc.log(`${cc.js.getClassName(this)} setInertia bool = ${bool}`)

        this.scrollView.inertia = bool
        this.scrollView.brake = brake
    }

    /**
     * 是否启用回弹
     * @param bool 是否启用
     * @param bounceDuration 回弹持续的时间，0 表示将立即反弹
     */
    setElastic(bool: boolean, bounceDuration: number) {
        cc.log(`${cc.js.getClassName(this)} setElasticEnabled bool = ${bool}`)

        this.scrollView.elastic = bool
        this.scrollView.bounceDuration = bounceDuration
    }

    /**
     * 追加列表项
     * @param num 项数
     */
    push(num: number) {
        // cc.log(`${cc.js.getClassName(this)} push num = ${num}`)

        this._totalCount += num
        if (this.isHorizontal) {
            this._totalCount >= 0 && this._addRightItems()
        } else {
            this._totalCount >= 0 && this._addBottomItems()
        }
    }

    /**
     * 设置初始填充位置是否反向
     * @param bool 是否反向
     */
    setStartFillPosReverse(bool: boolean) {
        cc.log(`${cc.js.getClassName(this)} setStartFillPosReverse bool = ${bool}`)

        this._startFillPosReverse = bool
    }

    /**
     * 停止自动滑动
     */
    stopAutoScroll() {
        // cc.log(`${cc.js.getClassName(this)} stopAutoScroll`)

        this.scrollView.stopAutoScroll()
    }

    /**
     * 是否正在手动滑动
     */
    isScrolling() {
        // cc.log(`${cc.js.getClassName(this)} isScrolling`)

        return this.scrollView.isScrolling()
    }

    /**
     * 列表容器是否已经填满视窗
     */
    isFull() {
        return this.isHorizontal ? (this.ndSvCtn.width > this.ndSvView.width) : (this.ndSvCtn.height > this.ndSvView.height)
    }

    /**
     * 锁定滑动
     * @param bool 是否锁定
     */
    lockScroll(bool: boolean) {
        // cc.log(`${cc.js.getClassName(this)} lockScroll bool = ${bool}`)

        this.scrollView[`moveLocked`] = bool
    }

    /**
     * 是否锁定滑动
     */
    isLockScroll() {
        // cc.log(`${cc.js.getClassName(this)} lockScroll bool = ${bool}`)

        return this.scrollView[`moveLocked`]
    }

    /**
     * 通过序号获取节点
     * @param tarIndex 序号
     */
    getItemByIndex(tarIndex: number) {
        // cc.log(`${cc.js.getClassName(this)} getItemByIndex index = ${index}`)

        return this._getItemByIndex(tarIndex)
    }

    /**
     * 是否启用滑动条
     * @param bool 是否启用
     * @param scrollBarEnableAutoHide 是否滑动条自动消失，默认否
     * @param scrollBarAutoHideTime 滑动条自动消失时间，秒，默认1秒
     */
    setScrollBar(bool: boolean, scrollBarEnableAutoHide = false, scrollBarAutoHideTime = 1) {
        cc.log(`${cc.js.getClassName(this)} setScrollBar bool = ${bool} scrollBarEnableAutoHide = ${scrollBarEnableAutoHide} scrollBarAutoHideTime = ${scrollBarAutoHideTime}`)

        this.isScrollBar = bool
        this.scrollBarEnableAutoHide = scrollBarEnableAutoHide
        this.scrollBarAutoHideTime = scrollBarAutoHideTime
    }

    // ===================================== 监听相关 ======================================

    /**
     * 绑定事件 BounceTop
     * @param call 回调
     */
    bindEventBounceTop(call: () => void) {
        cc.log(`${cc.js.getClassName(this)} bindEventBounceTop`)

        this._onBounceTop = call
    }

    /**
     * 绑定事件 BounceBottom
     * @param call 回调
     */
    bindEventBounceBottom(call: () => void) {
        cc.log(`${cc.js.getClassName(this)} bindEventBounceBottom`)

        this._onBounceBottom = call
    }

    /**
     * 绑定事件 ScrollBegan
     * @param call 回调
     */
    bindEventScrollBegan(call: () => void) {
        cc.log(`${cc.js.getClassName(this)} bindEventScrollBegan`)

        this._onScrollBegan = call
    }
}